﻿using System;
using System.IO.Compression;
using System.Net;
using System.Web;
using System.Web.Caching;
using System.IO;
using System.Diagnostics;
using DotNetBurner.Core;
using DotNetBurner.Web.Settings;

namespace DotNetBurner.Web
{
    public enum ResponseCompressionType
    {
        None,
        GZip,
        Deflate
    }

    public class StaticHandler : IHttpHandler
    {
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        public void ProcessRequest(HttpContext context)
        {
            var request = context.Request;
            var response = context.Response;

            if (request.HttpMethod == "POST")
            {
                throw new HttpException((int)HttpStatusCode.MethodNotAllowed, "Method not allowed");
            }

            string physicalPath = request.PhysicalPath;

            FileInfo file = new FileInfo(physicalPath);
            StaticType staticType = StaticHandlerSettings.GetStaticTypeForExtension(file.Extension.ToLowerInvariant());

            if (staticType == null)
                throw new HttpException((int)HttpStatusCode.UnsupportedMediaType,
                                        "Type of extension {0} not found. Please, configure it on web.config".FormatWith(file.Extension));

            ResponseCompressionType compressionType;

            if (staticType.Compress)
            {
                compressionType = GetClientCompressionType(context);

                if (compressionType == ResponseCompressionType.GZip)
                    response.AppendHeader("Content-encoding", "gzip");
                else if (compressionType == ResponseCompressionType.Deflate)
                    response.AppendHeader("Content-encoding", "deflate");
            }
            else
                compressionType = ResponseCompressionType.None;

            string key = "static-res-{0}-{1}".FormatWith(compressionType.ToString(), physicalPath);

            byte[] data;

            if (!Cache.TryGet(key, out data))
            {
                if (!file.Exists)
                    throw new HttpException((int)HttpStatusCode.NotFound, request.FilePath + " Not Found");

                Debug.WriteLine("{0} NOT DELIVERED from cache".FormatWith(file.Name));

                int len = (compressionType == ResponseCompressionType.None)
                              ? Convert.ToInt32(file.Length)
                              : Convert.ToInt32(file.Length / 3);

                using (MemoryStream memoryStream = new MemoryStream(len))
                {
                    using (Stream outputStream =
                            (compressionType == ResponseCompressionType.None ? memoryStream :
                            (compressionType == ResponseCompressionType.GZip ?
                                new GZipStream(memoryStream, CompressionMode.Compress, true) :
                                (Stream)new DeflateStream(memoryStream, CompressionMode.Compress))))
                    {
                        using (FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
                        {
                            int buffSize = Convert.ToInt32(Math.Min(file.Length, 8 * 1024));
                            byte[] buffer = new byte[buffSize];

                            int bytesRead;
                            while ((bytesRead = fs.Read(buffer, 0, buffSize)) > 0)
                            {
                                outputStream.Write(buffer, 0, bytesRead);
                            }
                        }

                        outputStream.Flush();
                    }

                    data = memoryStream.ToArray();

#if !DEBUG
                    Cache.CacheData(key, staticType.CacheProfile, data);
#endif

                }
            }
            else
            {
                Debug.WriteLine("{0} DELIVERED from cache".FormatWith(file.Name));
            }

#if !DEBUG
            context.CacheResponse(TimeSpan.FromDays(staticType.ClientCacheDuration));
#endif
            response.ContentType = staticType.MimeType;
            response.OutputStream.Write(data, 0, data.Length);
            response.OutputStream.Flush();
        }


        private static ResponseCompressionType GetClientCompressionType(HttpContext context)
        {
            var request = context.Request;

            string acceptEncoding = request.Headers["Accept-Encoding"];

            if (!string.IsNullOrEmpty(acceptEncoding))
            {
                if (acceptEncoding.IndexOf("GZIP", StringComparison.InvariantCultureIgnoreCase) != -1)
                    return ResponseCompressionType.GZip;

                if (acceptEncoding.IndexOf("DEFLATE", StringComparison.InvariantCultureIgnoreCase) != -1)
                    return ResponseCompressionType.Deflate;
            }

            return ResponseCompressionType.None;
        }

        private Cache Cache
        {
            get
            {
                return HttpRuntime.Cache;
            }
        }
    }
}
